package com.spring.demo.context;

import com.spring.demo.beans.*;
import com.spring.demo.beans.xml.BeanDefinitionParserDelegate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import sun.swing.StringUIClientPropertyKey;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by likuo on 2020/5/8.
 */
public class ClassPathXmlApplicationContext implements ApplicationContext {
    /** Synchronization monitor for the "refresh" and "destroy" */
    // 这个是为了线程锁
    private final Object startupShutdownMonitor = new Object();
    // 配置文件地址
    private String[] configLocations;

    public static final String NESTED_BEANS_ELEMENT = "beans";

    public static final String BEAN_ELEMENT = "bean";
    // 高仿 DefaultListableBeanFactory
    private final Map<String, AbstractBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    // 高仿 DefaultListableBeanFactory
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    //高仿DefaultSingletonBeanRegistry 用来存放单例的对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    public ClassPathXmlApplicationContext(String[] configLocations) {
        // 源码中做了相应的处理， 这里进行简化
        this.configLocations = configLocations;
        synchronized (this.startupShutdownMonitor) {
            try{
                // 读取配置文件
                Document document = loadConfig();
                //生成BeanDefinition
                createBeanDefinition(document);
                // IOC注入
                IocAutoWrite();
            }catch (Exception ex){
                    ex.printStackTrace();
            }
        }
    }
    // 这里进行注入
    private void IocAutoWrite() throws IllegalAccessException, NoSuchFieldException, InstantiationException {
        System.out.println("进行注入");
        for(String beanName:beanDefinitionNames){
            this.getBean(beanName);
        }
    }
    // 源码中就比较复杂， 这里就从简了哈。
    private void createBeanDefinition(Document document) throws ClassNotFoundException {
        System.out.println("初始化BeanDefinition");
        // 获取根节点
        Element documentElement = document.getDocumentElement();
        doRegisterBeanDefinitions(documentElement);
    }

    private void doRegisterBeanDefinitions(Element documentElement) throws ClassNotFoundException {
        // 看到这两个判断就豁然开朗了吧，此内容高仿 DefaultBeanDefinitionDocumentReader.parseDefaultElement方法，有兴趣的看一下哈。
        // 如果是<beans> 则解析下子元素
        if(NESTED_BEANS_ELEMENT.equals(documentElement.getNodeName())){
            NodeList childNodes = documentElement.getChildNodes();
            for(int i = 0 ;i<childNodes.getLength();i++){
                Node item = childNodes.item(i);
                if(item instanceof Element) {
                    doRegisterBeanDefinitions((Element)item);
                }

            }
        }else if(BEAN_ELEMENT.equals(documentElement.getNodeName())){
            AbstractBeanDefinition beanDefinition = new AbstractBeanDefinition();
            // 属性Id
            String beanName = documentElement.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
            // 类
            String className = documentElement.getAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE);
            // 设置值
            beanDefinition.setBeanClass(Class.forName(className));
            beanDefinition.setBeanName(beanName);
            // 值
            NodeList childNodes = documentElement.getChildNodes();
            if(childNodes!=null){
                List<PropertyValue> propertyValueList = new ArrayList<>();
                for(int i = 0 ;i<childNodes.getLength();i++){
                    Node item = childNodes.item(i);
                    if(item instanceof Element) {
                         Element property = (Element)item;
                        if(BeanDefinitionParserDelegate.PROPERTY_ELEMENT.equals(property.getNodeName())){
                            String propertyName = property.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE);
                            String propertyValue = property.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE);
                            propertyValueList.add(new PropertyValue(propertyName,propertyValue));
                        }
                    }
                }
                beanDefinition.setPropertyValues(new MutablePropertyValues(propertyValueList));
            }
            // 保存beanDefinition
            beanDefinitionMap.put(beanName,beanDefinition);
            beanDefinitionNames.add(beanName);
        }
    }

    // 加载配置
    private Document loadConfig() throws ParserConfigurationException, IOException, SAXException {
        System.out.println("加载配置文件");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 这里其实是一个数组，只是为了演示，这里就不循环处理了。
        InputStream inputStream = this.getFileInputStream(configLocations[0]);
        // 获取doc对象
        Document document= docBuilder.parse(inputStream);
        return document;
    }
    // 获取并处理文件路径
    private InputStream getFileInputStream(String configLocation) {
        // 真正的源码中spring 有对classpath* 和 classpath 这两个开头做了处理  PathMatchingResourcePatternResolver.getResources 可见
        int prefixEnd =   (configLocation.startsWith("war:") ? configLocation.indexOf("*/") + 1 :
                configLocation.indexOf(':') + 1);
        // this.getClass().getClassLoader().getResourceAsStream 这个函数寻找文件的起点是JAVA项目编译之后的根目录 ，也就是/target/class目录啦
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        return inputStream;
    }


    public ClassPathXmlApplicationContext(String configLocation) {
        this(new String[] {configLocation});
    }
    @Override
    public Object getBean(String name)   {
        // 如果存在则直接返回
        if(singletonObjects.containsKey(name)){
            return singletonObjects.get(name);
        }else{
            Object o = null;
            try {
                o = doCreateBean(name);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return o;
        }
    }

    private Object doCreateBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        // 现在只处理单例情况
        AbstractBeanDefinition beanDefinition = beanDefinitionMap.get(name);
        // 这次不说代理的情况，原本是使用cglib进行代理，这里简化下
        // 创建对象
         Class clazz = (Class) beanDefinition.getBeanClass();
         Object o = clazz.newInstance();
        // 设置属性
        if(beanDefinition.getPropertyValues()!=null){
            List<PropertyValue> propertyValueList = beanDefinition.getPropertyValues().getPropertyValueList();
            for(PropertyValue propertyValue:propertyValueList){
                String setMethod = this.getSetMethod(propertyValue.getName());
                Method method = clazz.getMethod(setMethod,String.class);
                method.invoke(o,propertyValue.getValue());
            }
        }
        singletonObjects.put(name,o);
        return o;
    }

    private String getSetMethod(String name) {
        return "set"+name.substring(0,1).toUpperCase()+name.substring(1);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws IllegalAccessException, NoSuchFieldException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 如果存在则直接返回
        if(singletonObjects.containsKey(name)){
            return (T) singletonObjects.get(name);
        }else{
            Object o = doCreateBean(name);
            return (T) o;
        }
    }
}
